namespace Plugins.Engine.Internal
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using Plugins.Core;
    using Plugins.Core.Interfaces;
    using Plugins.Interfaces;

    [Serializable]
    internal class PluginObjectActivator : IPluginObjectActivatorInternal, IPluginObjectActivator, IPluginAssemblyActivatorInternal
    {
        //private static readonly IXmlStringSerializer<byte[]> serializer = new XmlBinaryStringSerializer<byte[]>();

        [NonSerialized]
        private System.AppDomain _appDomain;

        private IPluginAssemblyInternal _assembly;
        private PluginTypeContainer _container;
        private readonly IPluginContextInternal _context;
        private readonly string _domain;
        private string _fileName;
        private byte[] _publicKey;
        private readonly IPluginObjectActivatorInternal _instance;
        private bool _isActive;
        private readonly Type _type;

        public PluginObjectActivator(string domain)
        {
            this._domain = domain;
        }

        protected PluginObjectActivator(Type type, IPluginContextInternal context, IPluginObjectActivatorInternal instance)
        {
            this._type = type;
            this._context = context;
            this._instance = instance;
        }

        public object GetObject(Type type)
        {
            try
            {
                object value = _appDomain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
                return value;
            }
            catch (Exception ex)
            {
                throw new PluginDomainException(ex);
            }
        }

        public void Activate()
        {
            lock (this)
            {
                if (!this._isActive)
                {
                    // TODO: Think about evidence
                    // Set up the AppDomainSetup
                    //AppDomainSetup setup = new AppDomainSetup();
                    //setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
                    //setup.ConfigurationFile = "config.xml";

                    // Set up the Evidence
                    //Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
                    //Evidence evidence = new Evidence(baseEvidence);
                    //evidence.AddAssembly("");
                    //evidence.AddHost("");
                    this._appDomain = AppDomain.CreateDomain(this._domain/*, baseEvidence, setup*/);
                    this._appDomain.AssemblyLoad += new AssemblyLoadEventHandler(appDomain_AssemblyLoad);
                    this._appDomain.AssemblyResolve += new ResolveEventHandler(appDomain_AssemblyResolve);
                    this._isActive = true;
                }
            }
        }

        public void Deactivate()
        {
            lock (this)
            {
                if (this._isActive)
                {
                    this._isActive = false;
                    AppDomain.Unload(this._appDomain);
                }
            }
        }

        public object GetObject()
        {
            return this._instance.GetObject(this.ActivationType);
        }

        public void ProcessService(Action<Type> action)
        {
            action(this._type);
        }

        public bool ProcessService(Predicate<Type> match)
        {
            return match(this._type);
        }

        bool IPluginAssemblyActivatorInternal.LoadAssembly(string fileName, byte[] publicKey)
        {
            if (this._isActive && this._fileName == null && fileName != null)
            {
                Type type = typeof(PluginTypeContainer);
                this._container = (PluginTypeContainer)this._appDomain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
                Assembly assembly = this._container.LoadAssembly(fileName);
                if (assembly != null)
                {
                    if (PluginObjectActivator.Equals(publicKey, assembly.GetName().GetPublicKeyToken()))
                    {
                        this._fileName = fileName;
                        this._publicKey = publicKey;
                        this._assembly = new PluginAssembly(this, assembly, publicKey);
                        return true;
                    }
                }
            }
            return false;
        }

        Assembly appDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            //throw new NotImplementedException();
            return null;
        }

        void appDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
        {
            //throw new NotImplementedException();
        }

        private static bool Equals(byte[] array, byte[] key)
        {
            if (key.Length != array.Length) return false;
            IEnumerator<byte> value = Array.AsReadOnly<byte>(key).GetEnumerator();
            foreach (byte element in array)
            {
                if (!(value.MoveNext() && (value.Current == element)))
                {
                    return false;
                }
            }
            return true;
        }

        bool IPluginAssemblyActivatorInternal.UnloadAssembly(string fileName, byte[] publicKey)
        {
            if (!this._isActive && this._fileName == fileName && _fileName != null)
            {
                if (PluginObjectActivator.Equals(_publicKey, _assembly.PublicKey))
                {
                    this._fileName = null;
                    this._container = null;
                    this._assembly = null;
                    return true;
                }
            }
            return false;
        }

        public Type ActivationType
        {
            get
            {
                return this._type;
            }
        }

        public IPluginObjectActivatorInternal InternalActivator
        {
            get
            {
                return this._instance;
            }
        }

        public IPluginContextInternal InternalContext
        {
            get
            {
                return this._context;
            }
        }

        public bool IsActive
        {
            get
            {
                //lock (this)
                //{
                return this._isActive;
                //}
            }
        }

        IPluginAssemblyInternal IPluginAssemblyActivatorInternal.Assembly
        {
            get
            {
                return this._assembly;
            }
        }

        #region IPluginObjectActivator Members

        void IPluginObjectActivator.ProcessObject(PluginObjectAction action)
        {
            if (_instance != null)
            {
                _instance.ProcessObject(action);
            }
        }

        bool IPluginObjectActivator.ProcessObject(PluginObjectPredicate match)
        {
            if (_instance != null)
            {
                return _instance.ProcessObject(match);
            }
            return false;
        }

        #endregion
    }
}

